<template>
  <div class="main">
    <div class="popup-select" @click.stop="openDialog" @keyup.enter="openDialog">
      <div v-if="multiple && modelValue && modelValue.length"
        :class="['popup-select-tags', tagsMultipleLine ? 'tagsMultipleLine' : 'tagsSingleLine']" ref="tagsRef">
        <el-tag v-for="(tag, tagIndex) in modelValue" :key="tag.value + '_' + tagIndex" type="info"
          :closable="!disabled" size="small" disable-transitions @close="handleDeleteTag(Number(tagIndex))">
          <span :title="showTooltip ? tag[showField] : ''"> {{ tag[showField] }} </span>
        </el-tag>
      </div>
      <el-tooltip :visible="inputHovering && tooltipVisible" :content="innerValue" placement="top">
        <el-input ref="referenceRef" class="popup-select-input" :value="innerValue" :placeholder="innerPlaceholder"
          :disabled="disabled" readonly :validate-event="false" @mouseenter="inputHovering = true"
          @mouseleave="inputHovering = false" input-style="cursor: pointer;">
          <template #suffix>
            <el-icon v-if="showClear && (inputHovering || dialogVisible)" class="icon-close" @click.stop="handleClear">
              <circle-close />
            </el-icon>
            <el-icon v-else>
              <ArrowDown />
            </el-icon>
          </template>
        </el-input>
      </el-tooltip>
    </div>

    <el-dialog v-model="dialogVisible" title="弹框选择" class="popop-select-dialog" width="70%" align-center append-to-body>
      <div class="dialog-body">
        <el-row class="search-box" :gutter="15" v-if="tableSearchList.length">
          <el-col :span="6" v-for="item in tableSearchList" :key="item.prop">
            <el-input v-if="!item.options" v-model="realQueryData[item.prop]" :placeholder="'请输入' + item.label"
              clearable @keyup.enter="search" />
            <el-select v-else v-model="realQueryData[item.prop]" :placeholder="'请选择' + item.label" clearable>
              <el-option v-for="o in item.options" :key="o.value" v-bind="o" />
            </el-select>
          </el-col>
          <div>
            <el-button type="primary" icon="search" @click="search"> 搜索 </el-button>
            <el-button icon="refresh-right" @click="reset"> 重置 </el-button>
          </div>
        </el-row>
        <el-table :data="tableData" @select="selectChange" @selectAll="selectChange" ref="dataTableRef"
          v-loading="tableLoading" border height="60vh">
          <el-table-column v-if="multiple" type="selection" width="55" fixed="left" align="center" />
          <el-table-column type="index" label="序号" width="55" fixed="left" align="center" />

          <el-table-column v-for="item in tableColumns" :key="item.prop" show-overflow-tooltip v-bind="item" />

          <el-table-column label="操作" width="90" fixed="right" align="center" v-if="!multiple">
            <template #default="scope">
              <el-button link :type="checkSelectable(scope) ? 'primary' : 'info'" @click="handleChange(scope.row)"
                size="default" :disabled="!checkSelectable(scope)">选择</el-button>
            </template>
          </el-table-column>
        </el-table>
        <div class="pagination">
          <el-pagination default background layout="total, sizes, prev, pager, next" :total="Number(tableDataTotal)"
            v-model:page-size="realQueryData.pageSize" v-model:current-page="realQueryData.pageNum"
            :page-sizes="[5, 10, 20, 50]" @change="getData" />
        </div>
      </div>
      <div class="dialog-footer" v-if="multiple">
        <el-button @click="handleCancel">取消</el-button>
        <el-button @click="handleChange" type="primary">提交</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script lang="ts" setup>
// 依赖
import { ElMessageBox, ElTable } from 'element-plus';
import { autoBlur, deepClone } from '@/utils/func';
import { type PropType, ref, computed, nextTick, onMounted, onUnmounted, watch } from 'vue';

// 参数和事件
const props = defineProps({
  modelValue: { // 显示内容
    type: [Object, Array, String],
    // required: true,
    // value格式：单选{ name: '张三', id: '3' } 多选[{ name: '张三', id: '3' }]
  },
  placeholder: { // 占位符
    type: String,
    default: '请选择'
  },
  multiple: { // 是否多选
    type: Boolean,
    default: false
  },
  disabled: { // 是否禁用
    type: Boolean,
    default: false
  },
  clearable: { // 是否显示清除按钮
    type: Boolean,
    default: false
  },
  showField: { // 作为展示内容的字段
    type: String,
    default: 'name'
  },
  showKey: { // 作为唯一标识的值
    type: String,
    default: 'id'
  },
  showTooltip: { // 内容超出可显示区域时，鼠标悬浮是否显示tooltip（多选模式显示title）
    type: Boolean,
    default: true
  },
  queryMethod: { // 列表数据请求方法
    type: Function,
    required: true
  },
  queryData: { // 列表数据初始请求体
    type: [Object, String, Array],
    required: true
  },
  dataFormatMethod: { // 数据格式化方法
    type: Function as PropType<(res: Res, queryData?: any) => Array<any>>,
  },
  tableColumns: { // 表格展示列
    type: Array as PropType<ObjectAny[]>,
    required: true
  },
  tableSearchList: { // 表格搜索条件
    type: Array as PropType<ObjectAny[]>,
    default: () => []
  },
  resetQueryData: { // 每次打开是否重置筛选条件和分页
    type: Boolean,
    default: false
  },
  tagsMultipleLine: { // 多选模式时，标签是否会换行
    type: Boolean,
    default: true
  },
})
const emit = defineEmits<{
  (e: 'change', value?: Object): void
  (e: 'update:modelValue', value?: Object): void
}>()

// ref
const dataTableRef = ref<InstanceType<typeof ElTable>>() // 表格ref
const referenceRef = ref(); // 表单元素ref
const tagsRef = ref(); // 标签盒子ref

// 组件属性和方法
const dialogVisible = ref(false) // 弹框是否显示
const inputHovering = ref(false) // 鼠标悬浮判断
const innerValue = computed(() => { // 表单元素显示内容
  if (!props.modelValue) {
    tooltipVisible.value = false
    return ''
  } else if (props.multiple) {
    tooltipVisible.value = false
    return ''
  } else {
    nextTick(() => { tootipDetermine() })
    return (props.modelValue as ObjectAny)[props.showField]
  }
})
const innerPlaceholder = computed(() => { // 占位符
  if (!props.multiple) {
    return props.placeholder
  } else {
    if (props.modelValue && props.modelValue.length) return ''
    return props.placeholder
  }
})
const showClear = computed(() => { // 是否显示删除按钮
  if (props.disabled) return false
  if (!props.multiple) {
    return props.modelValue && props.clearable
  } else {
    return props.modelValue && props.modelValue.length && props.clearable
  }
})
const handleClear = async () => { // 一键清空
  if (props.multiple) {
    const flag = await ElMessageBox.confirm(
      '确定清空所有内容吗?',
      '提示',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }
    ).catch(() => false)

    if (flag) {
      emit('update:modelValue', [])
      emit('change', [])
    }
  } else {
    emit('update:modelValue')
    emit('change')
  }
  autoBlur()
}
const tooltipVisible = ref(false) // 鼠标悬浮时是否展示tooltip
const tootipDetermine = () => { // 鼠标悬浮是否显示tooltip
  const innerValueWidth = referenceRef.value.input.scrollWidth - 1
  const inputWidth = referenceRef.value.input.clientWidth
  tooltipVisible.value = innerValueWidth > inputWidth && props.showTooltip
}
const resetInputHeight = () => { // 设置表单元素高度
  const input = referenceRef.value.input
  const tagsHeight = tagsRef.value ? Math.round(tagsRef.value.getBoundingClientRect().height) : 32;
  input.style.height = tagsHeight - 2 + 'px';
}
const handleDeleteTag = (index: any) => { // 从表单元素删除标签
  selectedData.splice(index, 1)
  selectedData = JSON.parse(JSON.stringify(selectedData));
  handleChange()
}
const openDialog = () => { // 打开弹窗
  if (props.disabled) return
  if (props.multiple && props.modelValue) selectedData = JSON.parse(JSON.stringify(props.modelValue))
  if (props.resetQueryData) realQueryData.value = deepClone(props.queryData as any[] | ObjectAny)
  dialogVisible.value = true
  getData()
}
const handleChange = (selectData?: Object) => { // 提交
  if (!props.multiple) {
    emit('update:modelValue', selectData)
    emit('change', selectData)
    dialogVisible.value = false
  } else {
    emit('update:modelValue', selectedData)
    emit('change', selectedData)
    dialogVisible.value = false
  }
  autoBlur()
}
const handleCancel = () => { // 取消
  dialogVisible.value = false
}
onMounted(() => { // 初始化组件
  realQueryData.value = deepClone(props.queryData as any[] | ObjectAny)
  if (props.multiple) {
    window.addEventListener("resize", resetInputHeight)
  } else {
    window.addEventListener("resize", tootipDetermine)
    if (innerValue.value) {
      nextTick(() => { tootipDetermine() })
    }
  }
})
onUnmounted(() => { // 解除组件绑定事件
  if (props.multiple) {
    window.removeEventListener("resize", resetInputHeight)
  } else {
    window.removeEventListener("resize", tootipDetermine)
  }
})
watch(() => props.modelValue, async () => { // 监听值变化
  await nextTick()
  resetInputHeight()
}, { immediate: true })

// 组件表格属性和方法
const tableData = ref<Object[]>([]) // 表格当前展示数据
const tableDataTotal = ref<Number>(0) // 表格当前展示数据条数
const tableLoading = ref<Boolean>(false) // 表格loading
const realQueryData = ref<ObjectAny>({}) // 当前请求体
let selectedData: ObjectAny[] = [] // 选中的表格数据
const getData = () => { // 获取数据
  tableLoading.value = true
  props.queryMethod(realQueryData.value).then((res: Res) => {
    if (props.dataFormatMethod) { tableData.value = props.dataFormatMethod(res, realQueryData.value) }
    else if (Array.isArray(res.data)) { tableData.value = res.data }
    else if (Array.isArray(res.data.list)) { tableData.value = res.data.list }
    else { console.error('[PopupSelect]: 未能找到可以渲染的数据，请检查接口或使用dataFormatMethod处理接口返回结果') }
    tableDataTotal.value = res.data.total
    tableLoading.value = false
    nextTick(() => { setSelection() })
  }).catch(() => {
    tableLoading.value = false
  })
}
const checkSelectable = (scope: ObjectAny) => { // 行是否可选
  if (!props.modelValue) return true
  return scope.row[props.showKey] !== (props.modelValue as ObjectAny)[props.showKey]
}
const setSelection = () => { // 设置应该被选中的列
  const tableSelected: any[] = []
  tableData.value.forEach((row: ObjectAny, i) => {
    if (selectedData.some(item => item[props.showKey] === row[props.showKey])) tableSelected.push(tableData.value[i])
  })
  tableSelected.forEach((row) => { dataTableRef.value!.toggleRowSelection(row, true) })
}
const selectChange = (data: ObjectAny[]) => { // 切换选中项
  // 选择的新数据与老数据重合，过滤掉已经存在过的新数据
  let newData = data.filter((item: ObjectAny) =>
    selectedData.every((item2: ObjectAny) => item2[props.showKey] !== item[props.showKey]))
  // 老数据中过滤掉 出现在当前表格中但是没有出现在data中 的数据
  selectedData = selectedData.filter((item: ObjectAny) =>
    !tableData.value.some((item2: ObjectAny) => item2[props.showKey] === item[props.showKey]) ||
    data.some((item2: ObjectAny) => item2[props.showKey] === item[props.showKey]))
  // 如果全选框状态切换至false，在旧数据中过滤掉本页数据
  if (!data.length) {
    newData = newData.filter((item: ObjectAny) =>
      !tableData.value.every((item2: ObjectAny) => item2[props.showKey] !== item[props.showKey]))
  }
  selectedData = selectedData.concat(newData)
}
const search = () => {
  Object.keys(realQueryData.value).forEach(key => {
    let item = realQueryData.value[key]
    realQueryData.value[key] = typeof item === 'string' ? item.trim() : item
  })
  realQueryData.value.pageNum = 1
  getData()
}
const reset = () => {
  realQueryData.value = deepClone(props.queryData as any[] | ObjectAny)
  getData()
}
</script>
<style lang="scss" scoped>
:deep(.popup-select) {
  width: 100%;
  font-size: 0;

  // :deep(.popup-select-input) {
  //   cursor: pointer !important;
  // }
  .icon-close {
    cursor: pointer;
  }

  .popup-select-tags {
    max-width: calc(100% - 22px);
    overflow: hidden;
    flex-wrap: wrap;
    position: absolute;
    background-color: transparent;
    z-index: 2;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    cursor: pointer;
    text-align: left;
    font-size: 14px;
    padding: 4px 12px;
    gap: 6px;
    min-height: 32px;
    line-height: 24px;
    border-radius: var(--el-border-radius-base);
    transition: var(--el-transition-duration);

    .el-tag {
      max-width: 100%;
      // overflow: hidden;
      // text-overflow: ellipsis;

      .el-tag__content {
        // max-width: calc(100%);
        overflow: hidden;
        text-overflow: ellipsis;
      }
    }
  }

  .tagsSingleLine {
    flex-wrap: nowrap;
    overflow-x: auto;
  }

  .el-input__wrapper {
    cursor: pointer;

  }

  .el-input__suffix {
    cursor: pointer;
  }
}

.popop-select-dialog {
  .search-box {
    height: 40px;
    align-items: center;
  }

  .pagination {
    display: flex;
    justify-content: right;
    text-align: right;
    padding: 10px 5px 0 10px;
  }
}
</style>